//  IDVI 1.0 source copyright 1996 Garth A. Dickie
//
//  This source is free for non-commercial use.  No warranty, etc.
//  Please acknowledge reuse by including the line:
//
//  "Based in part on IDVI 1.0 source copyright 1996 Garth A. Dickie"
//
//  in your documentation and source code.  For commercial use or
//  distribution, please contact the author.  Please also send
//  questions, comments, bug reports, or fixes.
//
//  Best Regards,
//  Garth A. Dickie
//  dickie@elastic.avid.com

package ibook.v10.idvi.font;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.MalformedURLException;
import java.util.Hashtable;

import ibook.v10.idvi.MessageContext;

public abstract class DVIFont {
    public static int debugFontCount;

    DVIFont( ) {
        debugFontCount ++;
    }

    public abstract DVICharacter getCharacter( int index );

    protected String key;

    //  static interface
    //
    //  A font cache is kept, shared by all documents.  There are actually
    //  two levels of checking for the cache:
    //
    //      First, we check whether the given font at the given size has
    //      been previously requested *from the given fontbase*.  If so,
    //      we return the previous result.  This eliminates a problem with
    //      font substitution -- if we want cmr10 at magstep 4 but it only
    //      exists at magsteps 1, 1.5, and 2, a long search takes place
    //      with many URLs requested, all of which fail.  We don't want to
    //      repeat this search for later pages using the same fontbase.
    //
    //      Second, we cache font name and size -> result.  This means that
    //      two documents with different fontbases may still share DVIFonts.
    //
    //  We assume that the fonts are located in a flat directory, with
    //  names of the form 'cmr10.300pk'.
    //
    //  When a font is read, the URL.openStream method returns a stream
    //  which is already buffered, so we don't need to buffer the stream.
    //  (This is different from opening a file...).

    final private static int        maxSizeOffset = 2;
    final private static int[ ]     sizeOffset =
        { 0, 1, -1, 2, -2 };

    final private static double[ ]  magstepOffset =
        { 0.0, -0.5, 0.5, -1.0, 1.0, -1.5, 1.5, -2.0, 2.0 };

    final private static int        kFontCacheInitialSize = 31;

    private static Hashtable    fontCacheLocal;
    private static Hashtable    fontCache;

    public static synchronized DVIFont getFont(
            URL fontBase, String name, int dpi, double magfactor, MessageContext context ) {
        
        if( fontCacheLocal == null )
            fontCacheLocal = new Hashtable( kFontCacheInitialSize );

        int size = ( int )( magfactor * dpi + 0.5 );
        String key = fontBase + name + "." + size;

        DVIFont result = ( DVIFont ) fontCacheLocal.get( key );

        if( result == null ) {
            result = getFontNoSearch( fontBase, name, size, context );

            if( result == null )
                result = getFontSearch( fontBase, name, size, dpi, magfactor, context );

            if( result != null )
                fontCacheLocal.put( key, result );
        }

        return result;
    }

    //  Load a font which was not available at the requested sizes (or nearby sizes).
    //  We compute the probable magstep number for the font, in units of one half
    //  magstep.  Then we search up and down from this magstep value.

    static DVIFont getFontSearch(
            URL fontBase, String name, int size, int dpi, double magfactor, MessageContext context ) {
        
        DVIFont result = null;

        double magbase = Math.log( 1.2 );
        double magstep = Math.floor( 2.0 * Math.log( magfactor ) / magbase + 0.5 ) / 2.0;

        int magstepIndex = 0;
        int newSize = ( int )( Math.exp( magbase * magstep ) * dpi + 0.5 );
        if( Math.abs( size - newSize ) <= maxSizeOffset )
            magstepIndex ++;
        
        while( magstepIndex < magstepOffset.length && result == null ) {
            double newMagstep = magstep + magstepOffset[ magstepIndex ++ ]; 
            newSize = ( int )( Math.exp( magbase * newMagstep ) * dpi + 0.5 );

            result = getFontNoSearch( fontBase, name, newSize, context );
        }

        if( result == null )
            context.showMessage( "Could not load font \"" + name + "\" at " + size + "dpi" );

        return result;
    }

    //  Load a font at the requested size.  If it already exists in our cache,
    //  we return the cached font.  Otherwise load it.  Note that the loadFont
    //  method does a little bit of searching, itself, but only by changing the
    //  request size by 1 or 2 up and down.

    public // just for testing
    static DVIFont getFontNoSearch(
            URL fontBase, String name, int size, MessageContext context ) {

        if( fontCache == null )
            fontCache = new Hashtable( kFontCacheInitialSize );

        String key = name + "." + size;

        DVIFont result = ( DVIFont )fontCache.get( key );

        if( result == null ) {
            result = loadFont( fontBase, name, size, context );

            if( result != null )
                fontCache.put( key, result );
        }

        return result;
    }

    //  Load a font, allowing that the size may be off by 1 or 2.

    private static DVIFont loadFont( URL fontBase, String name, int size, MessageContext context ) {
        int         sizeOffsetIndex = 0;
        InputStream inputStream = null;
        URL         fontURL = null;
        DVIFont     result = null;

        context.showMessage( "Loading font \"" + name + "\" at " + size + "dpi" );

        while( result == null && sizeOffsetIndex < sizeOffset.length ) {
            int newSize = size + sizeOffset[ sizeOffsetIndex ++ ];

            try {
                fontURL = new URL( fontBase, name + "." + newSize + "pk" );
            } catch( MalformedURLException e ) {
                System.err.println( "Corrupted DVI file? Bad font name \"" + name + "\"" );
                System.err.println( e );
                return null;
            }
            
            try {
                inputStream = fontURL.openStream( );
                result = new PKFont( inputStream, name, newSize );
            } catch( IOException e ) {
                // try again with another nearby size.

                // (not enough to catch IOException on openStream call alone, since
                // netscape won't throw an exception on missing file at this point --
                // only later, when we try to read from the stream.)
            } catch( PKFormatException e ) {
                System.err.println( "Corrupted font file at \"" + fontURL + "\"" );
                System.err.println( e );
                return null;
            }
        }

        return result;
    }




    private String      name;

    DVIFont( String name ) {
        this.name = name;
    }

    public String getName( ) {
        return name;
    }
}
